#ifndef XRPL_PROTOCOL_STXATTESTATIONS_H_INCLUDED
#define XRPL_PROTOCOL_STXATTESTATIONS_H_INCLUDED

#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/TER.h>

#include <boost/container/flat_set.hpp>
#include <boost/container/vector.hpp>

#include <cstddef>
#include <vector>

namespace ripple {

namespace Attestations {

struct AttestationBase
{
    // Account associated with the public key
    AccountID attestationSignerAccount;
    // Public key from the witness server attesting to the event
    PublicKey publicKey;
    // Signature from the witness server attesting to the event
    Buffer signature;
    // Account on the sending chain that triggered the event (sent the
    // transaction)
    AccountID sendingAccount;
    // Amount transfered on the sending chain
    STAmount sendingAmount;
    // Account on the destination chain that collects a share of the attestation
    // reward
    AccountID rewardAccount;
    // Amount was transfered on the locking chain
    bool wasLockingChainSend;

    explicit AttestationBase(
        AccountID attestationSignerAccount_,
        PublicKey const& publicKey_,
        Buffer signature_,
        AccountID const& sendingAccount_,
        STAmount const& sendingAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_);

    AttestationBase(AttestationBase const&) = default;

    virtual ~AttestationBase() = default;

    AttestationBase&
    operator=(AttestationBase const&) = default;

    // verify that the signature attests to the data.
    bool
    verify(STXChainBridge const& bridge) const;

protected:
    explicit AttestationBase(STObject const& o);
    explicit AttestationBase(Json::Value const& v);

    [[nodiscard]] static bool
    equalHelper(AttestationBase const& lhs, AttestationBase const& rhs);

    [[nodiscard]] static bool
    sameEventHelper(AttestationBase const& lhs, AttestationBase const& rhs);

    void
    addHelper(STObject& o) const;

private:
    [[nodiscard]] virtual std::vector<std::uint8_t>
    message(STXChainBridge const& bridge) const = 0;
};

// Attest to a regular cross-chain transfer
struct AttestationClaim : AttestationBase
{
    std::uint64_t claimID;
    std::optional<AccountID> dst;

    explicit AttestationClaim(
        AccountID attestationSignerAccount_,
        PublicKey const& publicKey_,
        Buffer signature_,
        AccountID const& sendingAccount_,
        STAmount const& sendingAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        std::uint64_t claimID_,
        std::optional<AccountID> const& dst_);

    explicit AttestationClaim(
        STXChainBridge const& bridge,
        AccountID attestationSignerAccount_,
        PublicKey const& publicKey_,
        SecretKey const& secretKey_,
        AccountID const& sendingAccount_,
        STAmount const& sendingAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        std::uint64_t claimID_,
        std::optional<AccountID> const& dst_);

    explicit AttestationClaim(STObject const& o);
    explicit AttestationClaim(Json::Value const& v);

    [[nodiscard]] STObject
    toSTObject() const;

    // return true if the two attestations attest to the same thing
    [[nodiscard]] bool
    sameEvent(AttestationClaim const& rhs) const;

    [[nodiscard]] static std::vector<std::uint8_t>
    message(
        STXChainBridge const& bridge,
        AccountID const& sendingAccount,
        STAmount const& sendingAmount,
        AccountID const& rewardAccount,
        bool wasLockingChainSend,
        std::uint64_t claimID,
        std::optional<AccountID> const& dst);

    [[nodiscard]] bool
    validAmounts() const;

private:
    [[nodiscard]] std::vector<std::uint8_t>
    message(STXChainBridge const& bridge) const override;

    friend bool
    operator==(AttestationClaim const& lhs, AttestationClaim const& rhs);
};

struct CmpByClaimID
{
    bool
    operator()(AttestationClaim const& lhs, AttestationClaim const& rhs) const
    {
        return lhs.claimID < rhs.claimID;
    }
};

// Attest to a cross-chain transfer that creates an account
struct AttestationCreateAccount : AttestationBase
{
    // createCount on the sending chain. This is the value of the `CreateCount`
    // field of the bridge on the sending chain when the transaction was
    // executed.
    std::uint64_t createCount;
    // Account to create on the destination chain
    AccountID toCreate;
    // Total amount of the reward pool
    STAmount rewardAmount;

    explicit AttestationCreateAccount(STObject const& o);

    explicit AttestationCreateAccount(Json::Value const& v);

    explicit AttestationCreateAccount(
        AccountID attestationSignerAccount_,
        PublicKey const& publicKey_,
        Buffer signature_,
        AccountID const& sendingAccount_,
        STAmount const& sendingAmount_,
        STAmount const& rewardAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        std::uint64_t createCount_,
        AccountID const& toCreate_);

    explicit AttestationCreateAccount(
        STXChainBridge const& bridge,
        AccountID attestationSignerAccount_,
        PublicKey const& publicKey_,
        SecretKey const& secretKey_,
        AccountID const& sendingAccount_,
        STAmount const& sendingAmount_,
        STAmount const& rewardAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        std::uint64_t createCount_,
        AccountID const& toCreate_);

    [[nodiscard]] STObject
    toSTObject() const;

    // return true if the two attestations attest to the same thing
    [[nodiscard]] bool
    sameEvent(AttestationCreateAccount const& rhs) const;

    friend bool
    operator==(
        AttestationCreateAccount const& lhs,
        AttestationCreateAccount const& rhs);

    [[nodiscard]] static std::vector<std::uint8_t>
    message(
        STXChainBridge const& bridge,
        AccountID const& sendingAccount,
        STAmount const& sendingAmount,
        STAmount const& rewardAmount,
        AccountID const& rewardAccount,
        bool wasLockingChainSend,
        std::uint64_t createCount,
        AccountID const& dst);

    [[nodiscard]] bool
    validAmounts() const;

private:
    [[nodiscard]] std::vector<std::uint8_t>
    message(STXChainBridge const& bridge) const override;
};

struct CmpByCreateCount
{
    bool
    operator()(
        AttestationCreateAccount const& lhs,
        AttestationCreateAccount const& rhs) const
    {
        return lhs.createCount < rhs.createCount;
    }
};

};  // namespace Attestations

// Result when checking when two attestation match.
enum class AttestationMatch {
    // One of the fields doesn't match, and it isn't the dst field
    nonDstMismatch,
    // all of the fields match, except the dst field
    matchExceptDst,
    // all of the fields match
    match
};

struct XChainClaimAttestation
{
    using TSignedAttestation = Attestations::AttestationClaim;
    static SField const& ArrayFieldName;

    AccountID keyAccount;
    PublicKey publicKey;
    STAmount amount;
    AccountID rewardAccount;
    bool wasLockingChainSend;
    std::optional<AccountID> dst;

    struct MatchFields
    {
        STAmount amount;
        bool wasLockingChainSend;
        std::optional<AccountID> dst;
        MatchFields(TSignedAttestation const& att);
        MatchFields(
            STAmount const& a,
            bool b,
            std::optional<AccountID> const& d)
            : amount{a}, wasLockingChainSend{b}, dst{d}
        {
        }
    };

    explicit XChainClaimAttestation(
        AccountID const& keyAccount_,
        PublicKey const& publicKey_,
        STAmount const& amount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        std::optional<AccountID> const& dst);

    explicit XChainClaimAttestation(
        STAccount const& keyAccount_,
        PublicKey const& publicKey_,
        STAmount const& amount_,
        STAccount const& rewardAccount_,
        bool wasLockingChainSend_,
        std::optional<STAccount> const& dst);

    explicit XChainClaimAttestation(TSignedAttestation const& claimAtt);

    explicit XChainClaimAttestation(STObject const& o);

    explicit XChainClaimAttestation(Json::Value const& v);

    AttestationMatch
    match(MatchFields const& rhs) const;

    [[nodiscard]] STObject
    toSTObject() const;

    friend bool
    operator==(
        XChainClaimAttestation const& lhs,
        XChainClaimAttestation const& rhs);
};

struct XChainCreateAccountAttestation
{
    using TSignedAttestation = Attestations::AttestationCreateAccount;
    static SField const& ArrayFieldName;

    AccountID keyAccount;
    PublicKey publicKey;
    STAmount amount;
    STAmount rewardAmount;
    AccountID rewardAccount;
    bool wasLockingChainSend;
    AccountID dst;

    struct MatchFields
    {
        STAmount amount;
        STAmount rewardAmount;
        bool wasLockingChainSend;
        AccountID dst;

        MatchFields(TSignedAttestation const& att);
    };

    explicit XChainCreateAccountAttestation(
        AccountID const& keyAccount_,
        PublicKey const& publicKey_,
        STAmount const& amount_,
        STAmount const& rewardAmount_,
        AccountID const& rewardAccount_,
        bool wasLockingChainSend_,
        AccountID const& dst_);

    explicit XChainCreateAccountAttestation(TSignedAttestation const& claimAtt);

    explicit XChainCreateAccountAttestation(STObject const& o);

    explicit XChainCreateAccountAttestation(Json::Value const& v);

    [[nodiscard]] STObject
    toSTObject() const;

    AttestationMatch
    match(MatchFields const& rhs) const;

    friend bool
    operator==(
        XChainCreateAccountAttestation const& lhs,
        XChainCreateAccountAttestation const& rhs);
};

// Attestations from witness servers for a particular claimid and bridge.
// Only one attestation per signature is allowed.
template <class TAttestation>
class XChainAttestationsBase
{
public:
    using AttCollection = std::vector<TAttestation>;

private:
    // Set a max number of allowed attestations to limit the amount of memory
    // allocated and processing time. This number is much larger than the actual
    // number of attestation a server would ever expect.
    static constexpr std::uint32_t maxAttestations = 256;
    AttCollection attestations_;

protected:
    // Prevent slicing to the base class
    ~XChainAttestationsBase() = default;

public:
    XChainAttestationsBase() = default;
    XChainAttestationsBase(XChainAttestationsBase const& rhs) = default;
    XChainAttestationsBase&
    operator=(XChainAttestationsBase const& rhs) = default;

    explicit XChainAttestationsBase(AttCollection&& sigs);

    explicit XChainAttestationsBase(Json::Value const& v);

    explicit XChainAttestationsBase(STArray const& arr);

    [[nodiscard]] STArray
    toSTArray() const;

    typename AttCollection::const_iterator
    begin() const;

    typename AttCollection::const_iterator
    end() const;

    typename AttCollection::iterator
    begin();

    typename AttCollection::iterator
    end();

    template <class F>
    std::size_t
    erase_if(F&& f);

    std::size_t
    size() const;

    bool
    empty() const;

    AttCollection const&
    attestations() const;

    template <class T>
    void
    emplace_back(T&& att);
};

template <class TAttestation>
[[nodiscard]] inline bool
operator==(
    XChainAttestationsBase<TAttestation> const& lhs,
    XChainAttestationsBase<TAttestation> const& rhs)
{
    return lhs.attestations() == rhs.attestations();
}

template <class TAttestation>
inline typename XChainAttestationsBase<TAttestation>::AttCollection const&
XChainAttestationsBase<TAttestation>::attestations() const
{
    return attestations_;
};

template <class TAttestation>
template <class T>
inline void
XChainAttestationsBase<TAttestation>::emplace_back(T&& att)
{
    attestations_.emplace_back(std::forward<T>(att));
};

template <class TAttestation>
template <class F>
inline std::size_t
XChainAttestationsBase<TAttestation>::erase_if(F&& f)
{
    return std::erase_if(attestations_, std::forward<F>(f));
}

template <class TAttestation>
inline std::size_t
XChainAttestationsBase<TAttestation>::size() const
{
    return attestations_.size();
}

template <class TAttestation>
inline bool
XChainAttestationsBase<TAttestation>::empty() const
{
    return attestations_.empty();
}

class XChainClaimAttestations final
    : public XChainAttestationsBase<XChainClaimAttestation>
{
    using TBase = XChainAttestationsBase<XChainClaimAttestation>;
    using TBase::TBase;
};

class XChainCreateAccountAttestations final
    : public XChainAttestationsBase<XChainCreateAccountAttestation>
{
    using TBase = XChainAttestationsBase<XChainCreateAccountAttestation>;
    using TBase::TBase;
};

}  // namespace ripple

#endif  // STXCHAINATTESTATIONS_H_
